using System;
using System.Collections;
using System.Collections.Generic;

namespace LightCAD.Three
{
    public class Sphere
    {
        #region scope properties or methods
        //private static Box3 _box = new Box3();
        //private static Vector3 _v1 = new Vector3();
        //private static Vector3 _v2 = new Vector3();
        private static SphereContext getContext()
            => ThreadContext.getCurrThreadContext().SphereCtx;
        #endregion

        #region Properties

        public Vector3 center;
        public double radius;

        #endregion

        #region constructor
        public Sphere(Vector3 center = null, double radius = -1)
        {
            if (center == null) center = new Vector3();
            this.center = center;
            this.radius = radius;
        }
        #endregion

        #region methods
        public Sphere set(Vector3 center, double radius)
        {
            this.center.copy(center);
            this.radius = radius;
            return this;
        }
        public Sphere setFromPoints(Vector3[] points, Vector3 optionalCenter=null)
        {
            Vector3 center = this.center;
            if (optionalCenter != null)
            {
                center.copy(optionalCenter);
            }
            else
            {
                getContext()._box.setFromPoints(points).getCenter(center);
            }
            double maxRadiusSq = 0;
            for (int i = 0, il = points.Length; i < il; i++)
            {
                maxRadiusSq = JMath.max(maxRadiusSq, center.distanceToSquared(points[i]));
            }
            this.radius = JMath.sqrt(maxRadiusSq);
            return this;
        }
        public Sphere copy(Sphere sphere)
        {
            this.center.copy(sphere.center);
            this.radius = sphere.radius;
            return this;
        }
        public bool isEmpty()
        {
            return (this.radius < 0);
        }
        public Sphere makeEmpty()
        {
            this.center.set(0, 0, 0);
            this.radius = -1;
            return this;
        }
        public bool containsPoint(Vector3 point)
        {
            return (point.distanceToSquared(this.center) <= (this.radius * this.radius));
        }
        public double distanceToPoint(Vector3 point)
        {
            return (point.distanceTo(this.center) - this.radius);
        }
        public bool intersectsSphere(Sphere sphere)
        {
            double radiusSum = this.radius + sphere.radius;
            return sphere.center.distanceToSquared(this.center) <= (radiusSum * radiusSum);
        }
        public bool intersectsBox(Box3 box)
        {
            return box.intersectsSphere(this);
        }
        public bool intersectsPlane(Plane plane)
        {
            return JMath.abs(plane.distanceToPoint(this.center)) <= this.radius;
        }
        public Vector3 clampPoint(Vector3 point, Vector3 target)
        {
            double deltaLengthSq = this.center.distanceToSquared(point);
            target.copy(point);
            if (deltaLengthSq > (this.radius * this.radius))
            {
                target.sub(this.center).normalize();
                target.multiplyScalar(this.radius).add(this.center);
            }
            return target;
        }
        public Box3 getBoundingBox(Box3 target)
        {
            if (this.isEmpty())
            {
                // Empty sphere produces empty bounding box
                target.makeEmpty();
                return target;
            }
            target.set(this.center, this.center);
            target.expandByScalar(this.radius);
            return target;
        }
        public Sphere applyMatrix4(Matrix4 matrix)
        {
            this.center.applyMatrix4(matrix);
            this.radius = this.radius * matrix.getMaxScaleOnAxis();
            return this;
        }
        public Sphere translate(Vector3 offset)
        {
            this.center.add(offset);
            return this;
        }
        public Sphere expandByPoint(Vector3 point)
        {
            if (this.isEmpty())
            {
                this.center.copy(point);
                this.radius = 0;
                return this;
            }
            var _v1 = getContext()._v1;
            _v1.subVectors(point, this.center);
            double lengthSq = _v1.lengthSq();
            if (lengthSq > (this.radius * this.radius))
            {
                // calculate the minimal sphere
                double length = JMath.sqrt(lengthSq);
                double delta = (length - this.radius) * 0.5;
                this.center.addScaledVector(_v1, delta / length);
                this.radius += delta;
            }
            return this;
        }
        public Sphere union(Sphere sphere)
        {
            if (sphere.isEmpty())
            {
                return this;
            }
            if (this.isEmpty())
            {
                this.copy(sphere);
                return this;
            }
            if (this.center.equals(sphere.center))
            {
                this.radius = JMath.max(this.radius, sphere.radius);
            }
            else
            {
                var ctx = getContext();
                var _v1 = ctx._v1;
                var _v2 = ctx._v2;
                _v2.subVectors(sphere.center, this.center).setLength(sphere.radius);
                this.expandByPoint(_v1.copy(sphere.center).add(_v2));
                this.expandByPoint(_v1.copy(sphere.center).sub(_v2));
            }
            return this;
        }
        public bool equals(Sphere sphere)
        {
            return sphere.center.equals(this.center) && (sphere.radius == this.radius);
        }
        public Sphere clone()
        {
            return new Sphere().copy(this);
        }
        #endregion

    }
}
